🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / feature / firmware / src / androidMain / kotlin / org / meshtastic / feature / firmware / AndroidFirmwareFileHandler.kt
Displaying Raw • Download
feature/firmware/src/androidMain/kotlin/org/meshtastic/feature/firmware/AndroidFirmwareFileHandler.kt 01cd54907d62cd90913d3d071b6bc240cae365ef (01cd5490) Text, 15.08 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.firmware
Tff7b72import T7ee787android.content.Context
Tff7b72import T7ee787android.provider.OpenableColumns
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787com.eygraber.uri.toAndroidUri
Tff7b72import T7ee787io.ktor.client.HttpClient
Tff7b72import T7ee787io.ktor.client.request.get
Tff7b72import T7ee787io.ktor.client.request.head
Tff7b72import T7ee787io.ktor.client.statement.bodyAsChannel
Tff7b72import T7ee787io.ktor.client.statement.bodyAsText
Tff7b72import T7ee787io.ktor.http.contentLength
Tff7b72import T7ee787io.ktor.http.isSuccess
Tff7b72import T7ee787io.ktor.utils.io.jvm.javaio.toInputStream
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.isActive
Tff7b72import T7ee787kotlinx.coroutines.withContext
Tff7b72import T7ee787org.koin.core.annotation.Single
Tff7b72import T7ee787org.meshtastic.core.common.util.CommonUri
Tff7b72import T7ee787org.meshtastic.core.common.util.ioDispatcher
Tff7b72import T7ee787org.meshtastic.core.model.DeviceHardware
Tff7b72import T7ee787java.io.File
Tff7b72import T7ee787java.io.FileOutputStream
Tff7b72import T7ee787java.io.IOException
Tff7b72import T7ee787java.io.InputStream
Tff7b72import T7ee787java.net.URI
Tff7b72import T7ee787java.util.zip.ZipEntry
Tff7b72import T7ee787java.util.zip.ZipInputStream
Tff7b72private Tff7b72const Tff7b72val Te6edf3DOWNLOAD_BUFFER_SIZE Tff7b72= T79c0ff8T79c0ff1T79c0ff9T79c0ff2
T8b949e/**
* Helper class to handle file operations related to firmware updates, such as downloading, copying from URI, and
* extracting specific files from Zip archives.
*/
Tf0883e@Single
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4)
Tff7b72class T56d364AndroidFirmwareFileHandlerTb4b4b4(Tff7b72private Tff7b72val Te6edf3contextTb4b4b4: Te6edf3ContextTb4b4b4, Tff7b72private Tff7b72val Te6edf3clientTb4b4b4: Te6edf3HttpClientTb4b4b4) Tb4b4b4: Te6edf3FirmwareFileHandler Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3tempDir Tff7b72= Te6edf3FileTb4b4b4(Te6edf3contextTb4b4b4.Te6edf3cacheDirTb4b4b4, Ta5d6ff"Ta5d6fffirmware_updateTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffcleanupAllTemporaryFilesTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3runCatching Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3tempDirTb4b4b4.Te6edf3existsTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3tempDirTb4b4b4.Te6edf3deleteRecursivelyTb4b4b4(Tb4b4b4)
Tb4b4b4}
Te6edf3tempDirTb4b4b4.Te6edf3mkdirsTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to cleanup temp directoryTa5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffcheckUrlExistsTb4b4b4(Te6edf3urlTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3clientTb4b4b4.Te6edf3headTb4b4b4(Te6edf3urlTb4b4b4)Tb4b4b4.Te6edf3statusTb4b4b4.Te6edf3isSuccessTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to check URL existence: Tffd700$Te6edf3urlTa5d6ff" Tb4b4b4}
Tff7b72false
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8fffetchTextTb4b4b4(Te6edf3urlTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657String? Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3response Tff7b72= Te6edf3clientTb4b4b4.Te6edf3getTb4b4b4(Te6edf3urlTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3responseTb4b4b4.Te6edf3statusTb4b4b4.Te6edf3isSuccessTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3responseTb4b4b4.Te6edf3bodyAsTextTb4b4b4(Tb4b4b4) Tff7b72else Tff7b72null
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to fetch text from: Tffd700$Te6edf3urlTa5d6ff" Tb4b4b4}
Tff7b72null
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffdownloadFileTb4b4b4(Te6edf3urlTb4b4b4: Tffa657StringTb4b4b4, Te6edf3fileNameTb4b4b4: Tffa657StringTb4b4b4, Te6edf3onProgressTb4b4b4: Tb4b4b4(Tffa657FloatTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tb4b4b4: Te6edf3FirmwareArtifact? Tff7b72=
Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3response Tff7b72=
Tff7b72try Tb4b4b4{
Te6edf3clientTb4b4b4.Te6edf3getTb4b4b4(Te6edf3urlTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffDownload failed for Tffd700$Te6edf3urlTa5d6ff" Tb4b4b4}
Tff7b72returnTf0883e@withContext Tff7b72null
Tb4b4b4}
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3responseTb4b4b4.Te6edf3statusTb4b4b4.Te6edf3isSuccessTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffDownload failed: Tffd700${Te6edf3responseTb4b4b4.Te6edf3statusTb4b4b4.Te6edf3valueTffd700}Ta5d6ff for Tffd700$Te6edf3urlTa5d6ff" Tb4b4b4}
Tff7b72returnTf0883e@withContext Tff7b72null
Tb4b4b4}
Tff7b72val Te6edf3body Tff7b72= Te6edf3responseTb4b4b4.Te6edf3bodyAsChannelTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3contentLength Tff7b72= Te6edf3responseTb4b4b4.Te6edf3contentLengthTb4b4b4(Tb4b4b4) Tff7b72?: Tff7b72-T79c0ff1L
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3tempDirTb4b4b4.Te6edf3existsTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3tempDirTb4b4b4.Te6edf3mkdirsTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3targetFile Tff7b72= Te6edf3javaTb4b4b4.Te6edf3ioTb4b4b4.Te6edf3FileTb4b4b4(Te6edf3tempDirTb4b4b4, Te6edf3fileNameTb4b4b4)
Te6edf3bodyTb4b4b4.Te6edf3toInputStreamTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3input Tff7b72-Tff7b72>
Te6edf3javaTb4b4b4.Te6edf3ioTb4b4b4.Te6edf3FileOutputStreamTb4b4b4(Te6edf3targetFileTb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3output Tff7b72-Tff7b72>
Tff7b72val Te6edf3buffer Tff7b72= Te6edf3ByteArrayTb4b4b4(Te6edf3DOWNLOAD_BUFFER_SIZETb4b4b4)
Tff7b72var Te6edf3bytesReadTb4b4b4: Tffa657Int
Tff7b72var Te6edf3totalBytesRead Tff7b72= T79c0ff0L
Tff7b72while Tb4b4b4(Te6edf3inputTb4b4b4.Te6edf3readTb4b4b4(Te6edf3bufferTb4b4b4)Tb4b4b4.Te6edf3also Tb4b4b4{ Te6edf3bytesRead Tff7b72= Tffa657it Tb4b4b4} Tff7b72!Tff7b72= Tff7b72-T79c0ff1Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3isActiveTb4b4b4) Tff7b72throw Te6edf3CancellationExceptionTb4b4b4(Ta5d6ff"Ta5d6ffDownload cancelledTa5d6ff"Tb4b4b4)
Te6edf3outputTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3bufferTb4b4b4, T79c0ff0Tb4b4b4, Te6edf3bytesReadTb4b4b4)
Te6edf3totalBytesRead Tff7b72+Tff7b72= Te6edf3bytesRead
Tff7b72if Tb4b4b4(Te6edf3contentLength Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3onProgressTb4b4b4(Te6edf3totalBytesReadTb4b4b4.Te6edf3toFloatTb4b4b4(Tb4b4b4) Tff7b72/ Te6edf3contentLengthTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3contentLength Tff7b72!Tff7b72= Tff7b72-T79c0ff1L Tff7b72&Tff7b72& Te6edf3totalBytesRead Tff7b72!Tff7b72= Te6edf3contentLengthTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3IOExceptionTb4b4b4(Ta5d6ff"Ta5d6ffIncomplete download: expected Tffd700$Te6edf3contentLengthTa5d6ff bytes, got Tffd700$Te6edf3totalBytesReadTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3targetFileTb4b4b4.Te6edf3toFirmwareArtifactTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffextractFirmwareFromZipTb4b4b4(
Te6edf3zipFileTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4,
Te6edf3hardwareTb4b4b4: Te6edf3DeviceHardwareTb4b4b4,
Te6edf3fileExtensionTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3preferredFilenameTb4b4b4: Tffa657String?Tb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3FirmwareArtifact? Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3localZipFile Tff7b72= Te6edf3zipFileTb4b4b4.Te6edf3toLocalFileOrNullTb4b4b4(Tb4b4b4) Tff7b72?: Tff7b72returnTf0883e@withContext Tff7b72null
Tff7b72val Te6edf3target Tff7b72= Te6edf3hardwareTb4b4b4.Te6edf3effectiveTarget
Tff7b72if Tb4b4b4(Te6edf3targetTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72& Te6edf3preferredFilename Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tff7b72returnTf0883e@withContext Tff7b72null
Tff7b72val Te6edf3targetLowerCase Tff7b72= Te6edf3targetTb4b4b4.Te6edf3lowercaseTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3preferredFilenameLower Tff7b72= Te6edf3preferredFilenameTff7b72?.Te6edf3lowercaseTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3matchingEntries Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3PairTff7b72<Te6edf3ZipEntryTb4b4b4, Te6edf3FileTff7b72>Tff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3tempDirTb4b4b4.Te6edf3existsTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3tempDirTb4b4b4.Te6edf3mkdirsTb4b4b4(Tb4b4b4)
Te6edf3ZipInputStreamTb4b4b4(Te6edf3localZipFileTb4b4b4.Te6edf3inputStreamTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3zipInput Tff7b72-Tff7b72>
Tff7b72var Te6edf3entry Tff7b72= Te6edf3zipInputTb4b4b4.Te6edf3nextEntry
Tff7b72while Tb4b4b4(Te6edf3entry Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3name Tff7b72= Te6edf3entryTb4b4b4.Te6edf3nameTb4b4b4.Te6edf3lowercaseTb4b4b4(Tb4b4b4)
T8b949e// File(name).name strips directory components, mitigating ZipSlip attacks
Tff7b72val Te6edf3entryFileName Tff7b72= Te6edf3FileTb4b4b4(Te6edf3nameTb4b4b4)Tb4b4b4.Te6edf3name
Tff7b72val Te6edf3isMatch Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3preferredFilenameLower Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3entryFileName Tff7b72=Tff7b72= Te6edf3preferredFilenameLower
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72!Te6edf3entryTb4b4b4.Te6edf3isDirectory Tff7b72&Tff7b72& Te6edf3isValidFirmwareFileTb4b4b4(Te6edf3nameTb4b4b4, Te6edf3targetLowerCaseTb4b4b4, Te6edf3fileExtensionTb4b4b4)
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3isMatchTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3outFile Tff7b72= Te6edf3FileTb4b4b4(Te6edf3tempDirTb4b4b4, Te6edf3entryFileNameTb4b4b4)
Te6edf3FileOutputStreamTb4b4b4(Te6edf3outFileTb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3output Tff7b72-Tff7b72> Te6edf3zipInputTb4b4b4.Te6edf3copyToTb4b4b4(Te6edf3outputTb4b4b4) Tb4b4b4}
Te6edf3matchingEntriesTb4b4b4.Te6edf3addTb4b4b4(Te6edf3entry Te6edf3to Te6edf3outFileTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3preferredFilenameLower Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72returnTf0883e@withContext Te6edf3outFileTb4b4b4.Te6edf3toFirmwareArtifactTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Te6edf3entry Tff7b72= Te6edf3zipInputTb4b4b4.Te6edf3nextEntry
Tb4b4b4}
Tb4b4b4}
T8b949e// Prefer the shortest matching entry name — official release bundles contain one
T8b949e// matching firmware per target; the heuristic picks the canonical name if multiple match.
Te6edf3matchingEntriesTb4b4b4.Te6edf3minByOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3firstTb4b4b4.Te6edf3nameTb4b4b4.Te6edf3length Tb4b4b4}Tff7b72?.Te6edf3secondTff7b72?.Te6edf3toFirmwareArtifactTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffextractFirmwareTb4b4b4(
Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4,
Te6edf3hardwareTb4b4b4: Te6edf3DeviceHardwareTb4b4b4,
Te6edf3fileExtensionTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3preferredFilenameTb4b4b4: Tffa657String?Tb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3FirmwareArtifact? Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3target Tff7b72= Te6edf3hardwareTb4b4b4.Te6edf3effectiveTarget
Tff7b72if Tb4b4b4(Te6edf3targetTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72& Te6edf3preferredFilename Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tff7b72returnTf0883e@withContext Tff7b72null
Tff7b72val Te6edf3targetLowerCase Tff7b72= Te6edf3targetTb4b4b4.Te6edf3lowercaseTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3preferredFilenameLower Tff7b72= Te6edf3preferredFilenameTff7b72?.Te6edf3lowercaseTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3matchingEntries Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3PairTff7b72<Te6edf3ZipEntryTb4b4b4, Te6edf3FileTff7b72>Tff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3tempDirTb4b4b4.Te6edf3existsTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3tempDirTb4b4b4.Te6edf3mkdirsTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3platformUri Tff7b72= Te6edf3uriTb4b4b4.Te6edf3toAndroidUriTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3inputStream Tff7b72= Te6edf3contextTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3openInputStreamTb4b4b4(Te6edf3platformUriTb4b4b4) Tff7b72?: Tff7b72returnTf0883e@withContext Tff7b72null
Te6edf3ZipInputStreamTb4b4b4(Te6edf3inputStreamTb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3zipInput Tff7b72-Tff7b72>
Tff7b72var Te6edf3entry Tff7b72= Te6edf3zipInputTb4b4b4.Te6edf3nextEntry
Tff7b72while Tb4b4b4(Te6edf3entry Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3name Tff7b72= Te6edf3entryTb4b4b4.Te6edf3nameTb4b4b4.Te6edf3lowercaseTb4b4b4(Tb4b4b4)
T8b949e// File(name).name strips directory components, mitigating ZipSlip attacks
Tff7b72val Te6edf3entryFileName Tff7b72= Te6edf3FileTb4b4b4(Te6edf3nameTb4b4b4)Tb4b4b4.Te6edf3name
Tff7b72val Te6edf3isMatch Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3preferredFilenameLower Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3entryFileName Tff7b72=Tff7b72= Te6edf3preferredFilenameLower
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72!Te6edf3entryTb4b4b4.Te6edf3isDirectory Tff7b72&Tff7b72& Te6edf3isValidFirmwareFileTb4b4b4(Te6edf3nameTb4b4b4, Te6edf3targetLowerCaseTb4b4b4, Te6edf3fileExtensionTb4b4b4)
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3isMatchTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3outFile Tff7b72= Te6edf3FileTb4b4b4(Te6edf3tempDirTb4b4b4, Te6edf3entryFileNameTb4b4b4)
Te6edf3FileOutputStreamTb4b4b4(Te6edf3outFileTb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3output Tff7b72-Tff7b72> Te6edf3zipInputTb4b4b4.Te6edf3copyToTb4b4b4(Te6edf3outputTb4b4b4) Tb4b4b4}
Te6edf3matchingEntriesTb4b4b4.Te6edf3addTb4b4b4(Te6edf3entry Te6edf3to Te6edf3outFileTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3preferredFilenameLower Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72returnTf0883e@withContext Te6edf3outFileTb4b4b4.Te6edf3toFirmwareArtifactTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Te6edf3entry Tff7b72= Te6edf3zipInputTb4b4b4.Te6edf3nextEntry
Tb4b4b4}
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3IOExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to extract firmware from URITa5d6ff" Tb4b4b4}
Tff7b72returnTf0883e@withContext Tff7b72null
Tb4b4b4}
Te6edf3matchingEntriesTb4b4b4.Te6edf3minByOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3firstTb4b4b4.Te6edf3nameTb4b4b4.Te6edf3length Tb4b4b4}Tff7b72?.Te6edf3secondTff7b72?.Te6edf3toFirmwareArtifactTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetFileSizeTb4b4b4(Te6edf3fileTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4)Tb4b4b4: Tffa657Long Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Te6edf3fileTb4b4b4.Te6edf3toLocalFileOrNullTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3existsTb4b4b4(Tb4b4b4) Tb4b4b4}Tff7b72?.Te6edf3lengthTb4b4b4(Tb4b4b4)
Tff7b72?: Te6edf3contextTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3openAssetFileDescriptorTb4b4b4(Te6edf3fileTb4b4b4.Te6edf3uriTb4b4b4.Te6edf3toAndroidUriTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffrTa5d6ff"Tb4b4b4)Tff7b72?.Te6edf3use Tb4b4b4{ Te6edf3descriptor Tff7b72-Tff7b72>
Te6edf3descriptorTb4b4b4.Te6edf3lengthTb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657it Tff7b72>Tff7b72= T79c0ff0L Tb4b4b4}
Tb4b4b4}
Tff7b72?: T79c0ff0L
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffdeleteFileTb4b4b4(Te6edf3fileTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4) Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3fileTb4b4b4.Te6edf3isTemporaryTb4b4b4) Tff7b72returnTf0883e@withContext
Tff7b72val Te6edf3localFile Tff7b72= Te6edf3fileTb4b4b4.Te6edf3toLocalFileOrNullTb4b4b4(Tb4b4b4) Tff7b72?: Tff7b72returnTf0883e@withContext
Tff7b72if Tb4b4b4(Te6edf3localFileTb4b4b4.Te6edf3existsTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3localFileTb4b4b4.Te6edf3deleteTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffreadBytesTb4b4b4(Te6edf3artifactTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4)Tb4b4b4: Te6edf3ByteArray Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3localFile Tff7b72= Te6edf3artifactTb4b4b4.Te6edf3toLocalFileOrNullTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3localFile Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3localFileTb4b4b4.Te6edf3existsTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3localFileTb4b4b4.Te6edf3readBytesTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3contextTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3openInputStreamTb4b4b4(Te6edf3artifactTb4b4b4.Te6edf3uriTb4b4b4.Te6edf3toAndroidUriTb4b4b4(Tb4b4b4)Tb4b4b4)Tff7b72?.Te6edf3use Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3readBytesTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72?: Tff7b72throw Te6edf3IOExceptionTb4b4b4(Ta5d6ff"Ta5d6ffCannot open artifact: Tffd700${Te6edf3artifactTb4b4b4.Te6edf3uriTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffimportFromUriTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4)Tb4b4b4: Te6edf3FirmwareArtifact? Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3inputStream Tff7b72= Te6edf3contextTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3openInputStreamTb4b4b4(Te6edf3uriTb4b4b4.Te6edf3toAndroidUriTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72?: Tff7b72returnTf0883e@withContext Tff7b72null
Tff7b72val Te6edf3tempFile Tff7b72= Te6edf3FileTb4b4b4(Te6edf3contextTb4b4b4.Te6edf3cacheDirTb4b4b4, Ta5d6ff"Ta5d6fffirmware_update/ota_firmware.binTa5d6ff"Tb4b4b4)
Te6edf3tempFileTb4b4b4.Te6edf3parentFileTff7b72?.Te6edf3mkdirsTb4b4b4(Tb4b4b4)
Te6edf3inputStreamTb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3input Tff7b72-Tff7b72> Te6edf3tempFileTb4b4b4.Te6edf3outputStreamTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3output Tff7b72-Tff7b72> Te6edf3inputTb4b4b4.Te6edf3copyToTb4b4b4(Te6edf3outputTb4b4b4) Tb4b4b4} Tb4b4b4}
Te6edf3tempFileTb4b4b4.Te6edf3toFirmwareArtifactTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetDisplayNameTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4)Tb4b4b4: Tffa657String? Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3platformUri Tff7b72= Te6edf3uriTb4b4b4.Te6edf3toAndroidUriTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3platformUriTb4b4b4.Te6edf3scheme Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4) Tb4b4b4{
T8b949e// query() can throw SecurityException (revoked permission) or IllegalArgumentException
T8b949e// (malformed URI) even after the scheme guard; fall through to the file-scheme branch on failure.
Te6edf3runCatching Tb4b4b4{
Te6edf3contextTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3queryTb4b4b4(
Te6edf3platformUriTb4b4b4,
Te6edf3arrayOfTb4b4b4(Te6edf3OpenableColumnsTb4b4b4.Te6edf3DISPLAY_NAMETb4b4b4)Tb4b4b4,
Tff7b72nullTb4b4b4,
Tff7b72nullTb4b4b4,
Tff7b72nullTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to query display name from content providerTa5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4.Te6edf3getOrNullTb4b4b4(Tb4b4b4)
Tff7b72?.Te6edf3use Tb4b4b4{ Te6edf3cursor Tff7b72-Tff7b72>
Tff7b72val Te6edf3nameIndex Tff7b72= Te6edf3cursorTb4b4b4.Te6edf3getColumnIndexTb4b4b4(Te6edf3OpenableColumnsTb4b4b4.Te6edf3DISPLAY_NAMETb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3nameIndex Tff7b72>Tff7b72= T79c0ff0 Tff7b72&Tff7b72& Te6edf3cursorTb4b4b4.Te6edf3moveToFirstTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3cursor
Tb4b4b4.Te6edf3getStringTb4b4b4(Te6edf3nameIndexTb4b4b4)
Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72?.Te6edf3let Tb4b4b4{
Tff7b72returnTf0883e@withContext Tffa657it
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3platformUriTb4b4b4.Te6edf3scheme Tff7b72=Tff7b72= Ta5d6ff"Ta5d6fffileTa5d6ff"Tb4b4b4) Te6edf3uriTb4b4b4.Te6edf3pathSegmentsTb4b4b4.Te6edf3lastOrNullTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4} Tff7b72else Tff7b72null
Tb4b4b4}
T8b949e/**
* Fully expands [artifact] into memory, keyed by entry name.
*
* Streams from the artifact rather than buffering it whole, and delegates the bounds to [extractZipEntriesBounded]
* so this and the desktop handler cannot drift apart. The [getFileSize] check is only a cheap early rejection — it
* returns 0 for a provider that declines to report a length, so the inflation bound inside the extractor is what
* actually protects the heap.
*/
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffextractZipEntriesTb4b4b4(Te6edf3artifactTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4)Tb4b4b4: Te6edf3MapTff7b72<Tffa657StringTb4b4b4, Te6edf3ByteArrayTff7b72> Tff7b72=
Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3declaredSize Tff7b72= Te6edf3getFileSizeTb4b4b4(Te6edf3artifactTb4b4b4)
Te6edf3requireTb4b4b4(Te6edf3declaredSize Tff7b72<Tff7b72= Te6edf3MAX_FIRMWARE_ZIP_BYTESTb4b4b4) Tb4b4b4{
Ta5d6ff"Ta5d6ffFirmware archive is Tffd700$Te6edf3declaredSizeTa5d6ff bytes, over the Tffd700$Te6edf3MAX_FIRMWARE_ZIP_BYTESTa5d6ff limitTa5d6ff"
Tb4b4b4}
Te6edf3openArtifactStreamTb4b4b4(Te6edf3artifactTb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3extractZipEntriesBoundedTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e/** Opens [artifact] for streaming, preferring a local file and falling back to the content resolver. */
Tff7b72private Tff7b72fun Td2a8ffopenArtifactStreamTb4b4b4(Te6edf3artifactTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4)Tb4b4b4: Te6edf3InputStream Tb4b4b4{
Tff7b72val Te6edf3localFile Tff7b72= Te6edf3artifactTb4b4b4.Te6edf3toLocalFileOrNullTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3localFile Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3localFileTb4b4b4.Te6edf3existsTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return Te6edf3localFileTb4b4b4.Te6edf3inputStreamTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3contextTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3openInputStreamTb4b4b4(Te6edf3artifactTb4b4b4.Te6edf3uriTb4b4b4.Te6edf3toAndroidUriTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72?: Tff7b72throw Te6edf3IOExceptionTb4b4b4(Ta5d6ff"Ta5d6ffCannot open artifact: Tffd700${Te6edf3artifactTb4b4b4.Te6edf3uriTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffisValidFirmwareFileTb4b4b4(Te6edf3filenameTb4b4b4: Tffa657StringTb4b4b4, Te6edf3targetTb4b4b4: Tffa657StringTb4b4b4, Te6edf3fileExtensionTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72=
Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3featureTb4b4b4.Te6edf3firmwareTb4b4b4.Te6edf3isValidFirmwareFileTb4b4b4(Te6edf3filenameTb4b4b4, Te6edf3targetTb4b4b4, Te6edf3fileExtensionTb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffcopyToUriTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4, Te6edf3destinationUriTb4b4b4: Te6edf3CommonUriTb4b4b4)Tb4b4b4: Tffa657Long Tff7b72=
Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3inputStream Tff7b72=
Te6edf3sourceTb4b4b4.Te6edf3toLocalFileOrNullTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3inputStreamTb4b4b4(Tb4b4b4)
Tff7b72?: Te6edf3contextTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3openInputStreamTb4b4b4(Te6edf3sourceTb4b4b4.Te6edf3uriTb4b4b4.Te6edf3toAndroidUriTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72?: Tff7b72throw Te6edf3IOExceptionTb4b4b4(Ta5d6ff"Ta5d6ffCannot open source URITa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3outputStream Tff7b72=
Te6edf3contextTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3openOutputStreamTb4b4b4(Te6edf3destinationUriTb4b4b4.Te6edf3toAndroidUriTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72?: Tff7b72throw Te6edf3IOExceptionTb4b4b4(Ta5d6ff"Ta5d6ffCannot open content URI for writingTa5d6ff"Tb4b4b4)
Te6edf3inputStreamTb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3input Tff7b72-Tff7b72> Te6edf3outputStreamTb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3output Tff7b72-Tff7b72> Te6edf3inputTb4b4b4.Te6edf3copyToTb4b4b4(Te6edf3outputTb4b4b4) Tb4b4b4} Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Te6edf3FileTb4b4b4.Td2a8fftoFirmwareArtifactTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3FirmwareArtifact Tff7b72=
Te6edf3FirmwareArtifactTb4b4b4(Te6edf3uri Tff7b72= Te6edf3CommonUriTb4b4b4.Te6edf3parseTb4b4b4(Te6edf3toURITb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4, Te6edf3fileName Tff7b72= Te6edf3nameTb4b4b4, Te6edf3isTemporary Tff7b72= Tff7b72trueTb4b4b4)
Tff7b72private Tff7b72fun Te6edf3FirmwareArtifactTb4b4b4.Td2a8fftoLocalFileOrNullTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3File? Tb4b4b4{
Tff7b72val Te6edf3uriString Tff7b72= Te6edf3uriTb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3uriStringTb4b4b4.Te6edf3startsWithTb4b4b4(Ta5d6ff"Ta5d6fffile:Ta5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3runCatching Tb4b4b4{ Te6edf3FileTb4b4b4(Te6edf3URITb4b4b4(Te6edf3uriStringTb4b4b4)Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3getOrNullTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.14s